function Compiler(reader,quickCompile) {
	this.reader = reader;
	this.queue = [];
	this.line = 1;
	this.character = 0;
	this.info = [];
	this.nodes = [];
	this.symbols = {};
	this.scope = this.symbols;
	if (quickCompile) this.quickCompile = quickCompile;
}

Compiler.prototype.addSymbol = function(node) {	
	if (this.scope[node.name]) 
		throw new CompilerError('Redefinition of symbol \'' + node.name + '\'');
	this.scope[node.name] = node;
}

Compiler.prototype.pushScope = function(node) {
	this.addSymbol(node);
	this.scope = node.symbols = { '^': this.scope };
}

Compiler.prototype.popScope = function() {
	this.scope = this.scope['^'];
}

Compiler.prototype.compile = function() {
	try {
		while (true) {
			this.nodes.push(this.parseMainElement());
		}
	} catch (ex) {
		if (ex instanceof EndOfFileException) return;
		this.reportError(ex);
		return null;
	}
};

Compiler.prototype.reportError = function(ex) {
	var msg = '<b>Error:</b> ' + ex.message + ' at line: ' + this.line + 
		', character: ' + this.character + ' near to \'' + this.buffer  + '\':\n<div class="code">',
		last = this.info.length - 3;
	try {
		for (var i = 0; i < 10; i++) this.token();
	} catch (ex) {
		// NOOP
	}
	for (i = 1; i < this.info.length; i++) {
		msg += (i == last ? '<b>' : '') + this.info[i] + (i == last ? '</b>' : '');
	}
	System.log(msg.replace(/\n\n/g,'\n').replace(/\t/g,'  ') + '</div>',false);
}


// parser

Compiler.prototype.parseMainElement = function() {
	var access = null, token;
	if (this.lookahead(1) == Compiler.TOKEN_PACKAGE) {
		this.token(); // package
		while (true) {
			this.eatUpToken(Compiler.TOKEN_ID);			
			this.scope = this.scope[this.buffer] || (this.scope[this.buffer] = {});
			token = this.token();
			if (token == Compiler.TOKEN_SCO) break;
			if (token != Compiler.TOKEN_DOT) throw new CompilerError("Sysntax error");
		}
	}
	while (true) {
		token = this.token();
		switch (token) {
			case Compiler.TOKEN_IMPORT:
				break;
			case Compiler.TOKEN_PROTECTED:
			case Compiler.TOKEN_STATIC:
				throw new CompilerError("Invalid modifier");
			case Compiler.TOKEN_PUBLIC:
			case Compiler.TOKEN_PRIVATE:
				if (access != null)
					throw new CompilerError("Duplicate modifier");
				access = token;
				break;
			case Compiler.TOKEN_VAR:
				throw new CompilerError("Invalid variable declaration");
			case Compiler.TOKEN_FUNCTION:
				var sn = this.parseFunction();
				sn.access = access || Compiler.TOKEN_PRIVATE;
				return sn;
			case Compiler.TOKEN_CLASS:
				var sn = this.parseClass();
				sn.access = access || Compiler.TOKEN_PRIVATE;
				return sn;
			default: 
				throw new CompilerError("Syntax error");
		}
	}
}

Compiler.prototype.parseClass = function() {
	var node = { type: Compiler.TOKEN_CLASS, members: [] };
	this.eatUpToken(Compiler.TOKEN_ID); // class name
	node.name = this.buffer;
	this.pushScope(node);
	token = this.token();
	if (token == Compiler.TOKEN_EXTENDS) {
		this.eatUpToken(Compiler.TOKEN_ID); // parent class
		node.parent = this.buffer;
		token = this.token();
	}
	if (token == Compiler.TOKEN_IMPLEMENTS) {
		this.eatUpToken(Compiler.TOKEN_ID); // first inmplemented interface
		node.interfaces = [ this.buffer ];
		token = this.token();
		while (token == Compiler.TOKEN_COM) {
			this.eatUpToken(Compiler.TOKEN_ID); // more implemented interface
			node.interfaces.push(this.buffer);
			token = this.token();
		}
	} else {
		node.interfaces = [];
	}
	if (token != Compiler.TOKEN_BLO) {
		throw new CompilerError("Syntax error");
	}
	while (this.lookahead(1) != Compiler.TOKEN_BLC) {
		node.members.push(this.parseMember());
	}	
	this.token(); // close block
	this.popScope();
	// cosntructor check
	if (node.symbols[node.name] && node.symbols[node.name].type == Compiler.TOKEN_FUNCTION &&
			node.symbols[node.name].isStatic) 
		throw new BuildErrorException('Constructor can not be static');		
	return node;
}

Compiler.prototype.parseMember = function() {
	var node, access = null, isStatic = null;
	while (true) {
		var token = this.token();
		switch (token) {
			case Compiler.TOKEN_PUBLIC:
			case Compiler.TOKEN_PROTECTED:
			case Compiler.TOKEN_PRIVATE:
				if (access != null)
					throw new CompilerError("Duplicate modifier");
				access = token;
				break;
			case Compiler.TOKEN_STATIC:
				if (isStatic != null)
					throw new CompilerError("Duplicate modifier");
				isStatic = true;
				break;
			case Compiler.TOKEN_VAR:
				isStatic = isStatic === null ? false : isStatic;
				access = access === null ? Compiler.TOKEN_PROTECTED : access;
				node = this.parseVariable(access,isStatic);
				this.eatUpToken(Compiler.TOKEN_SCO);
				return { type: Compiler.TOKEN_VAR, isStatic: isStatic, 
					access: access, variables: node };
			case Compiler.TOKEN_FUNCTION:
				node = this.parseFunction();
				node.isStatic = isStatic || false;
				node.access = access || Compiler.TOKEN_PROTECTED;
				return node;
			case Compiler.TOKEN_CLASS:
				node = this.parseClass();
				node.isStatic = isStatic || false;
				node.access = access || Compiler.TOKEN_PROTECTED;
				return node;
			default: 
				throw new CompilerError("Syntax error");
		}
	}
}

Compiler.prototype.parseVariable = function(access,isStatic,variables) {
	variables = variables || [];
	var node = { value: null };
	if (arguments.length > 0) {
		node.access = access;
		node.isStatic = isStatic;
	}
	variables.push(node);
	this.eatUpToken(Compiler.TOKEN_ID); // variable name	
	node.name = this.buffer;
	this.addSymbol(node);
	if (this.lookahead(1) == Compiler.TOKEN_COL) {
		this.token(); // :
		this.eatUpToken(Compiler.TOKEN_ID); // variable type
		node.dataType = this.buffer;
	}
	if (this.lookahead(1) == Compiler.TOKEN_LET) {
		this.token();
		node.initValue = this.parseAssignmentExpression();
	}
	if (this.lookahead(1) == Compiler.TOKEN_COM) {
		this.token();
		this.parseVariable.apply(arguments);
	}
	// variable does not parse the closing semicolon!
	return variables;
}

Compiler.prototype.parseFunction = function() {
	var node = { type: Compiler.TOKEN_FUNCTION };
	this.eatUpToken(Compiler.TOKEN_ID); // function name
	node.name = this.buffer;
	this.pushScope(node);
	this.eatUpToken(Compiler.TOKEN_BRO);
	// parameters
	if (this.lookahead(1) != Compiler.TOKEN_BRC) {
		node.parameters = this.parseVariable();
	}
	this.eatUpToken(Compiler.TOKEN_BRC);
	if (this.lookahead(1) == Compiler.TOKEN_COL) {
		this.token();
		this.eatUpToken(Compiler.TOKEN_ID); // return type
		node.dataType = this.buffer;
	}
	if (this.quickCompile) {
		this.quickParseBlock();
	} else {
		node.block = this.parseBlock();
	}
	this.popScope();
	return node;
}

Compiler.prototype.quickParseBlock = function() {
	var depth = 1, token;
	this.eatUpToken(Compiler.TOKEN_BLO); // {
	while (depth > 0) {
		token = this.token();
		if (token = Compiler.TOKEN_BLO) depth++;
		else if (token = Compiler.TOKEN_BLC) depth--;
	}
}

Compiler.prototype.parseBlock = function() {
	var statement, node = { type: Compiler.TOKEN_BLO, statements: [] }; 
	this.eatUpToken(Compiler.TOKEN_BLO); // {
	while (this.lookahead(1) != Compiler.TOKEN_BLC) {
		statement = this.parseStatement();
		if (statement) node.statements.push(statement);
	}
	this.token(); // }
	return node;
}

Compiler.prototype.parseStatement = function() {
	var node;
	switch (this.lookahead(1)) {
		case Compiler.TOKEN_SCO:
			this.token();
			return null;
		case Compiler.TOKEN_BLO:
			return this.parseBlock();
		case Compiler.TOKEN_IF:
			return this.parseIf();
		case Compiler.TOKEN_SWITCH:
			return this.parseSwitch();
		case Compiler.TOKEN_FOR:
			return this.parseFor();
		case Compiler.TOKEN_DO:
			return this.parseDo();
		case Compiler.TOKEN_WHILE:
			return this.parseWhile();
		case Compiler.TOKEN_RETURN:
			return this.parseReturn();
		case Compiler.TOKEN_TRY:
			return this.parseTry();
		case Compiler.TOKEN_BREAK:
		case Compiler.TOKEN_CONTINUE:
			node = { type: this.token() }; 
			if (this.lookahead(1) == Compiler.TOKEN_ID) {
				this.token(); // id
				node.label = this.buffer;
			}
			this.eatUpToken(Compiler.TOKEN_SCO);
			return node;
		case Compiler.TOKEN_ID:
			if (this.lookahead(2) == Compiler.TOKEN_COL) {
				this.token(); // id
				var label = this.buffer;
				this.token(); // :
				return { type: Compiler.TOKEN_LABEL, label: label }; 
			}
		default:
			node = this.parseVariablesOrExpression();
			this.eatUpToken(Compiler.TOKEN_SCO);
			return node;
	}
}

Compiler.prototype.parseCondition = function() {
	this.eatUpToken(Compiler.TOKEN_BRO);
	var expr = this.parseExpression();
	this.eatUpToken(Compiler.TOKEN_BRC);
	if (expr) return expr;
	throw new CompilerError("Syntax error");
}

Compiler.prototype.parseIf = function() {
	var node = { type: Compiler.TOKEN_IF };
	this.token(); // if
	node.condition = this.parseCondition();
	node.statIf = this.parseStatement();
	if (this.lookahead(1) == Compiler.TOKEN_ELSE) {
		this.token();
		node.statElse = this.parseStatement();
	}
	return node;
}

Compiler.prototype.parseSwitch = function() {
	var node = { type: Compiler.TOKEN_SWITCH, blocks: [] };
	this.token(); // switch
	this.eatUpToken(Compiler.TOKEN_BRO);
	node.expr = this.parseExpression();
	this.eatUpToken(Compiler.TOKEN_BRC);
	this.eatUpToken(Compiler.TOKEN_BLO);
	while (this.lookahead(1) != Compiler.TOKEN_BLC) {
		node.blocks.push(this.parseCase());
	}
	this.token(); // }
	return node;
}

Compiler.prototype.parseCase = function() {
	var node = { statements: [] }, 
		token = this.token(), statement;
	if (token == Compiler.TOKEN_CASE) {
		node.expr = this.parseExpression();
	} else if (token != Compiler.TOKEN_DEFAULT) {
		throw new CompilerError("Syntax error");
	}
	node.type = token;
	this.eatUpToken(Compiler.TOKEN_COL);
	token = this.lookahead(1);
	while (token != Compiler.TOKEN_BLC && token != Compiler.TOKEN_CASE && 
			token != Compiler.TOKEN_DEFAULT) {
		statement = this.parseStatement();
		if (statement) node.statements.push(statement);
		token = this.lookahead(1);
	}
	return node;
}

Compiler.prototype.parseFor = function() {
	var node = { type: Compiler.TOKEN_FOR };
	this.token(); // for
	this.eatUpToken(Compiler.TOKEN_BRO);
	node.expr1 = this.parseVariablesOrExpression();
	this.eatUpToken(Compiler.TOKEN_SCO);
	node.expr2 = this.parseExpression();
	this.eatUpToken(Compiler.TOKEN_SCO);
	node.expr3 = this.parseExpression();
	this.eatUpToken(Compiler.TOKEN_BRC);
	node.statement = this.parseStatement();
	return node;
}

Compiler.prototype.parseDo = function() {
	var node = { type: Compiler.TOKEN_DO };
	this.token(); // do
	node.statement = this.parseStatement();
	this.eatUpToken(Compiler.TOKEN_WHILE);
	node.condition = this.parseCondition();
	this.eatUpToken(Compiler.TOKEN_SCO);
	return node;
}

Compiler.prototype.parseWhile = function() {
	var node = { type: Compiler.TOKEN_WHILE };
	this.token(); // while
	node.condition = this.parseCondition();
	node.statement = this.parseStatement();
	return node;
}

Compiler.prototype.parseTry = function() {
	var node = { type: Compiler.TOKEN_TRY, catchBlocks: [] };
	this.token(); // try
	node.statement = this.parseStatement();
	while (this.lookahead(1) == Compiler.TOKEN_CATCH) {
		var sn = { type: Compiler.TOKEN_CATCH };
		this.token(); // catch
		this.eatUpToken(Compiler.TOKEN_BRO);
		this.eatUpToken(Compiler.TOKEN_ID); // variable name
		sn.name = this.buffer;
		if (this.lookahead(1) == Compiler.TOKEN_COL) {
			this.token(); // :
			this.eatUpToken(Compiler.TOKEN_ID); // variable type
			sn.dataType = this.buffer;
		}
		this.eatUpToken(Compiler.TOKEN_BRC);
		sn.statement = this.parseStatement();
		node.catchBlocks.push(sn);
	}
	if (this.lookahead(1) == Compiler.TOKEN_FINALLY) {
		this.token(); // finally
		node.finallyStat = this.parseStatement();
	}
	if (!node.finallyStat && node.catchBlocks.length == 0) 
		throw new CompilerError("Missing catch or finally after try");
	return node;
}

Compiler.prototype.parseReturn = function() {
	var node = { type: Compiler.TOKEN_RETURN };
	this.token() // return;
	if (this.lookahead(1) != Compiler.TOKEN_SCO) {
		node.expr = this.parseExpression();
	}
	this.eatUpToken(Compiler.TOKEN_SCO);
	return node;
}

Compiler.prototype.parseVariablesOrExpression = function() {
	if (this.lookahead(1) == Compiler.TOKEN_VAR) {
		this.token();
		return { type: Compiler.TOKEN_VAR, 
			variables: this.parseVariable(), lvalue: false };
	}
	return this.parseExpression();
}

Compiler.prototype.parseExpression = function() {
	var expr = this.parseSimpleExpression(),
		op = this.lookahead(1);  
	while (expr && op == Compiler.TOKEN_COM) {
		this.token(); // ,
		expr = { type: op, left: expr, right: this.parseAssignmentExpression(), lvalue: false }; 
		op = this.lookahead(1);
	}
	return expr;
}

Compiler.prototype.parseSimpleExpression = function() {
	return this.parseAssignmentExpression();
}

Compiler.prototype.parseAssignmentExpression = function() {
	var expr = this.parseRelationalExpression(),
		op = this.lookahead(1);
	while (expr && (op == Compiler.TOKEN_LET || op == Compiler.TOKEN_LAD || 
			op == Compiler.TOKEN_LSU || op == Compiler.TOKEN_LMU ||
			op == Compiler.TOKEN_LDI || op == Compiler.TOKEN_LMO)) {
		if (!expr.lvalue) throw new CompilerError("Invalid assignment left-hand side");
		this.token();
		expr = { type: op, left: expr, right: this.parseAssignmentExpression(), lvalue: false }; 
		op = this.lookahead(1);
	}
	return expr;
}

Compiler.prototype.parseRelationalExpression = function() {
	var expr = this.parseAddExpression(),
		op = this.lookahead(1);
	while (expr && (op == Compiler.TOKEN_LES || op == Compiler.TOKEN_GRE || 
			op == Compiler.TOKEN_LEQ || op == Compiler.TOKEN_GRQ)) {
		this.token();
		expr = { type: op, left: expr, right: this.parseAddExpression(), lvalue: false }; 
		op = this.lookahead(1);
	}
	return expr;
}

Compiler.prototype.parseAddExpression = function() {
	var expr = this.parseMulExpression(),
		op = this.lookahead(1);
	while (expr && (op == Compiler.TOKEN_ADD || op == Compiler.TOKEN_SUB)) {
		this.token();
		expr = { type: op, left: expr, right: this.parseMulExpression(), lvalue: false }; 
		op = this.lookahead(1);
	}
	return expr;
}

Compiler.prototype.parseMulExpression = function() {
	var expr = this.parseAccessExpression(),
		op = this.lookahead(1);
	while (expr && (op == Compiler.TOKEN_MUL || op == Compiler.TOKEN_DIV || 
			op == Compiler.TOKEN_MOD)) {
		this.token();
		expr = { type: op, left: expr, right: this.parseAccessExpression(), lvalue: false }; 
		op = this.lookahead(1);
	}
	return expr;
}

Compiler.prototype.parseAccessExpression = function() {
	var expr = this.parsePrimaryExpression(),
		op = this.lookahead(1);
	while (expr && (op == Compiler.TOKEN_DOT || op == Compiler.TOKEN_SBO)) {
		this.token();
		if (op == Compiler.TOKEN_SBO) {
			expr = { type: op, left: expr, right: this.parseExpression(), lvalue: true };
			this.eatUpToken(Compiler.TOKEN_SBC);
		} else {
			expr = { type: op, left: expr, right: this.parsePrimaryExpression(), lvalue: true }; 
		}
		op = this.lookahead(1);
	}
	return expr;
}

Compiler.prototype.parsePrimaryExpression = function() {
	switch (this.lookahead(1)) {
		case Compiler.TOKEN_BRO:
			this.token();
			var expr = this.parseExpression();
			this.eatUpToken(Compiler.TOKEN_BRC);
			return expr;
		case Compiler.TOKEN_BLO:
			return this.parseMap();
		case Compiler.TOKEN_SBO:			
			return this.parseArray();
		case Compiler.TOKEN_NUMBER:
		case Compiler.TOKEN_STRING: 
			return { type: this.token(), value: this.buffer, lvalue: false };
		case Compiler.TOKEN_ID: 
			return { type: this.token(), name: this.buffer, lvalue: true };
		default:
			return null;
	}
}

Compiler.prototype.parseMap = function() {
	var node = { type: Compiler.TOKEN_MAP, values: [], lvalue: false };
	this.token(); // {
	if (this.lookahead(1) != Compiler.TOKEN_BLC) {
		var sn = { type: Compiler.TOKEN_MAPVAL, key: this.parseExpression() };
		this.eatUpToken(Compiler.TOKEN_COL);
		sn.value =  this.parseSimpleExpression();
		node.values.push(sn);
		while (this.lookahead(1) != Compiler.TOKEN_BLC) {
			this.eatUpToken(Compiler.TOKEN_COM);
			if (this.lookahead(1) == Compiler.TOKEN_BLC) break;
			sn = { type: Compiler.TOKEN_MAPVAL, key: this.parseExpression() };
			this.eatUpToken(Compiler.TOKEN_COL);
			sn.value =  this.parseSimpleExpression();
			node.values.push(sn);
		}
	}
	this.token(); // }
	return node;
}

Compiler.prototype.parseArray = function() {
	var node = { type: Compiler.TOKEN_ARRAY, values: [], lvalue: false };
	this.token(); // [
	if (this.lookahead(1) != Compiler.TOKEN_SBC) {
		node.values.push(this.parseSimpleExpression());
		while (this.lookahead(1) != Compiler.TOKEN_SBC) {
			this.eatUpToken(Compiler.TOKEN_COM);
			if (this.lookahead(1) == Compiler.TOKEN_SBC) break;
			node.values.push(this.parseSimpleExpression());
		}
	}
	this.token(); // ]
	return node;
}


// lexer

Compiler.LINE_COMMENT = 10;
Compiler.BLOCK_COMMENT = 11;
Compiler.TOKEN_SYMBOLS = 12;

Compiler.TOKEN_ID = 100;
Compiler.TOKEN_NUMBER = 101;
Compiler.TOKEN_STRING = 102;
Compiler.TOKEN_MAP = 103;
Compiler.TOKEN_MAPVAL = 104;
Compiler.TOKEN_ARRAY = 105;
                                 
Compiler.TOKEN_PUBLIC = 110;
Compiler.TOKEN_PROTECTED = 111;
Compiler.TOKEN_PRIVATE = 112;
Compiler.TOKEN_STATIC = 113;
Compiler.TOKEN_VAR = 114;
Compiler.TOKEN_FUNCTION = 115;
Compiler.TOKEN_PACKAGE = 116;
Compiler.TOKEN_CLASS = 117;
Compiler.TOKEN_EXTENDS = 118;
Compiler.TOKEN_IMPLEMENTS = 119;

Compiler.TOKEN_IF = 120;
Compiler.TOKEN_ELSE = 121;
Compiler.TOKEN_SWITCH = 122;
Compiler.TOKEN_CASE = 123;
Compiler.TOKEN_DEFAULT = 124;
Compiler.TOKEN_BREAK = 125;
Compiler.TOKEN_FOR = 126;
Compiler.TOKEN_IN = 127;
Compiler.TOKEN_DO = 128;
Compiler.TOKEN_WHILE = 129;
Compiler.TOKEN_CONTINUE = 130;
Compiler.TOKEN_RETURN = 131;
Compiler.TOKEN_NEW = 132;
Compiler.TOKEN_INSTANCEOF = 133;
Compiler.TOKEN_LABEL = 134;
Compiler.TOKEN_TRY = 135;
Compiler.TOKEN_CATCH = 136;
Compiler.TOKEN_FINALLY = 137;
Compiler.TOKEN_THROW = 137;

Compiler.TOKEN_ADD = 140;
Compiler.TOKEN_SUB = 141;
Compiler.TOKEN_MUL = 142;
Compiler.TOKEN_DIV = 143;
Compiler.TOKEN_MOD = 144;
Compiler.TOKEN_INC = 145;
Compiler.TOKEN_DEC = 146;
Compiler.TOKEN_NEG = 147;
                           
Compiler.TOKEN_EQU = 150;
Compiler.TOKEN_NEQ = 151;
Compiler.TOKEN_LES = 152;
Compiler.TOKEN_GRE = 153;
Compiler.TOKEN_LEQ = 154;
Compiler.TOKEN_GRQ = 155;
                           
Compiler.TOKEN_LET = 160;
Compiler.TOKEN_LAD = 161;
Compiler.TOKEN_LSU = 162;
Compiler.TOKEN_LMU = 163;
Compiler.TOKEN_LDI = 164;
Compiler.TOKEN_LMO = 165;
Compiler.TOKEN_CND = 165;
                           
Compiler.TOKEN_NOT = 170;
Compiler.TOKEN_LOR = 171;
Compiler.TOKEN_AND = 172;
                           
Compiler.TOKEN_BLO = 180;
Compiler.TOKEN_BLC = 181;
Compiler.TOKEN_BRO = 182;
Compiler.TOKEN_BRC = 183;
Compiler.TOKEN_SBO = 184;
Compiler.TOKEN_SBC = 185;
Compiler.TOKEN_COL = 186;
Compiler.TOKEN_SCO = 187;
Compiler.TOKEN_COM = 188;
Compiler.TOKEN_DOT = 189;

Compiler.KEYWORDS = {
	'public': Compiler.TOKEN_PUBLIC,
	'protected': Compiler.TOKEN_PROTECTED,
	'private': Compiler.TOKEN_PRIVATE,
	'static': Compiler.TOKEN_STATIC,
	'var': Compiler.TOKEN_VAR,
	'function': Compiler.TOKEN_FUNCTION,
	'package': Compiler.TOKEN_PACKAGE,
	'class': Compiler.TOKEN_CLASS,
	'extends': Compiler.TOKEN_EXTENDS,
	'implements': Compiler.TOKEN_IMPLEMENTS,
	'if': Compiler.TOKEN_IF,
	'else': Compiler.TOKEN_ELSE,
	'switch': Compiler.TOKEN_SWITCH,
	'case': Compiler.TOKEN_CASE,
	'default': Compiler.TOKEN_DEFAULT,
	'break': Compiler.TOKEN_BREAK,
	'for': Compiler.TOKEN_FOR,
	'in': Compiler.TOKEN_IN,
	'do': Compiler.TOKEN_DO,
	'while': Compiler.TOKEN_WHILE,
	'continue': Compiler.TOKEN_CONTINUE,
	'return': Compiler.TOKEN_RETURN,
	'new': Compiler.TOKEN_RETURN,
	'try': Compiler.TOKEN_TRY,
	'catch': Compiler.TOKEN_CATCH,
	'finally': Compiler.TOKEN_FINALLY,
	'throw': Compiler.TOKEN_THROW
};

Compiler.PRECEDENCE = {};

Compiler.PRECEDENCE[Compiler.TOKEN_DOT] = 1;
Compiler.PRECEDENCE[Compiler.TOKEN_SBO] = 1;
Compiler.PRECEDENCE[Compiler.TOKEN_NEW] = 1;

Compiler.PRECEDENCE[Compiler.TOKEN_BRO] = 2;

Compiler.PRECEDENCE[Compiler.TOKEN_INC] = 3;
Compiler.PRECEDENCE[Compiler.TOKEN_DEC] = 3;

Compiler.PRECEDENCE[Compiler.TOKEN_NOT] = 4;
Compiler.PRECEDENCE[Compiler.TOKEN_BNO] = 4;
Compiler.PRECEDENCE[Compiler.TOKEN_NEG] = 4;
Compiler.PRECEDENCE[Compiler.TOKEN_UNP] = 4;

Compiler.PRECEDENCE[Compiler.TOKEN_MUL] = 5;
Compiler.PRECEDENCE[Compiler.TOKEN_DIV] = 5;
Compiler.PRECEDENCE[Compiler.TOKEN_MOD] = 5;

Compiler.PRECEDENCE[Compiler.TOKEN_ADD] = 6;
Compiler.PRECEDENCE[Compiler.TOKEN_SUB] = 6;

Compiler.PRECEDENCE[Compiler.TOKEN_LES] = 7;
Compiler.PRECEDENCE[Compiler.TOKEN_GRE] = 7;
Compiler.PRECEDENCE[Compiler.TOKEN_LEQ] = 7;
Compiler.PRECEDENCE[Compiler.TOKEN_GRQ] = 7;
Compiler.PRECEDENCE[Compiler.TOKEN_INSTANCEOF] = 7;

Compiler.PRECEDENCE[Compiler.TOKEN_EQU] = 8;
Compiler.PRECEDENCE[Compiler.TOKEN_NEQ] = 8;

Compiler.PRECEDENCE[Compiler.TOKEN_AND] = 9;

Compiler.PRECEDENCE[Compiler.TOKEN_LOR] = 10;

Compiler.PRECEDENCE[Compiler.TOKEN_CND] = 11;

Compiler.PRECEDENCE[Compiler.TOKEN_LET] = 12;
Compiler.PRECEDENCE[Compiler.TOKEN_LAD] = 12;
Compiler.PRECEDENCE[Compiler.TOKEN_LSU] = 12;
Compiler.PRECEDENCE[Compiler.TOKEN_LMU] = 12;
Compiler.PRECEDENCE[Compiler.TOKEN_LDI] = 12;
Compiler.PRECEDENCE[Compiler.TOKEN_LMO] = 12;

Compiler.PRECEDENCE[Compiler.TOKEN_COM] = 13;

Compiler.OPERATORS = {
	'.': Compiler.TOKEN_DOT,
	'?': Compiler.TOKEN_CND,
	'{': Compiler.TOKEN_BLO,
	'}': Compiler.TOKEN_BLC,
	'(': Compiler.TOKEN_BRO,
	')': Compiler.TOKEN_BRC,
	'[': Compiler.TOKEN_SBO,
	']': Compiler.TOKEN_SBC,
	':': Compiler.TOKEN_COL,
	';': Compiler.TOKEN_SCO,
	',': Compiler.TOKEN_COM,
	'+': { 
		'+': Compiler.TOKEN_ADD,
		'++': Compiler.TOKEN_INC,
		'+=': Compiler.TOKEN_LAD 
	},
	'-': { 
		'-': Compiler.TOKEN_SUB,
		'--': Compiler.TOKEN_DEC,
		'+=': Compiler.TOKEN_LSU 
	},
	'*': {
		'*': Compiler.TOKEN_MUL,
		'*=': Compiler.TOKEN_LMU
	},
	'%': {
		'%': Compiler.TOKEN_MOD,
		'%=': Compiler.TOKEN_LMO
	},
	'/': {
		'/': Compiler.TOKEN_DIV,
		'/=': Compiler.TOKEN_LDI,
		'//': Compiler.LINE_COMMENT,
		'/*': Compiler.BLOCK_COMMENT
	},
	'=': {
		'=': Compiler.TOKEN_LET,
		'==': Compiler.TOKEN_EQU
	},
	'!': {
		'!': Compiler.TOKEN_NOT,
		'!=': Compiler.TOKEN_NEQ
	},
	'<': {
		'<': Compiler.TOKEN_LES,
		'<=': Compiler.TOKEN_LEQ
	},
	'>': {
		'>': Compiler.TOKEN_GRE,
		'>=': Compiler.TOKEN_GRQ
	}
};

Compiler.prototype.lookahead = function(pos) {
	for (var i = pos - this.queue.length; i > 0; i--) 
		this.queue.push({ token: this.lex(), buffer: this.buffer });
	return this.queue[pos - 1].token;
}

Compiler.prototype.eatUpToken = function(token) {
	if (token != this.token()) throw new CompilerError("Syntax error");
}

Compiler.prototype.token = function() {
	var token;
	if (this.queue.length) {
		var item = this.queue.shift();
		this.buffer = item.buffer;
		token = item.token;
	} else {
		token =  this.lex();
	}
	if (this.info.length > 30) this.info = this.info.slice(2);
	this.info.push(this.ws);
	this.info.push(this.buffer);
	return token;
}

Compiler.prototype.lex = function() {
	this.ws = '';
	this.buffer = '';
	var c = this.lastChar || ' ';
	while (c.match(/\s/)) {
		this.ws += c;
		c = this.read();
	}
	if (c.match(/[a-zA-Z]/)) { // id or keyword
		do {
			this.buffer += c;
			c = this.read();
		} while (c.match(/[a-zA-Z0-9_]/));
		this.lastChar = c;
		if (Compiler.KEYWORDS[this.buffer]) return Compiler.KEYWORDS[this.buffer];
		else return Compiler.TOKEN_ID;
	} else if (c.match(/[0-9]/)) { // intneger or float
		do {
			this.buffer += c;
			c = this.read();
		} while (c.match(/[0-9\.]/));
		this.lastChar = c;
		return Compiler.TOKEN_NUMBER;
	} else if (c == '"' || c == "'") { // string
		while (c != (sc = this.read())) {
			if (sc == '\\') {
				sc = this.read();
				if (sc == 'n' || sc == 'r' || sc == 't' || sc == 'b' || 
						sc == 'f' || sc == '"' || sc == '\\') {
					this.buffer += '\\' + sc;
				} else if (sc == '\'') {
					this.buffer += sc;
				} else {
					throw new CompilerError("Unknown escape character");
				}
			} else if (sc == '"') {
				this.buffer += '\\"';
			} else {
				this.buffer += sc;
			}
		}
		this.lastChar = this.read();
		return Compiler.TOKEN_STRING;
	} else {
		this.buffer = c;
		this.lastChar = this.read();
		var op = Compiler.OPERATORS[c];
		if (op) {
			if (op instanceof Object) {
				if (op[c + this.lastChar]) {
					this.buffer += this.lastChar;
					this.lastChar = this.read();
					op = op[this.buffer];
					// comments
					if (op == Compiler.LINE_COMMENT) {
						while (c != '\n') c = this.read();
						return this.token();
					}
					if (op == Compiler.BLOCK_COMMENT) {
						while (true) {
							c = this.read();
							if (c == '*') {
								c = this.read();
								if (c == '/') {
									return this.token();
								}
							}
						}
					}
					return op;
				}
				return op[c];
			} else {
				return op;
			}
		} 
		throw new CompilerError("Syntax error");
	}
};

// reader

Compiler.prototype.read = function() {
	var cc = this.reader.read();
	this.character++;
	if (cc == Reader.EOF) {
		if (this.eof == true) {
			throw new EndOfFileException();
		}	
		this.eof = true;
		return ' ';
	}
	var c = String.fromCharCode(cc);
	if (c == '\n') {
		this.line++;
		this.character = 0;
	}
	return c;	
};

